﻿/*  CamHelper (See http://camhelper.codeplex.com/ or https://bitbucket.org/AndiKS/camhelper for more Info)
    Copyright (C) 2011 Andreas Wimmer

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Globalization;

namespace CamHelper.Interpreter
{

    #region Statement

    public abstract class Statement : IComparable<Statement>
    {
        public int StartToken { get; set; }
        public int EndToken { get; set; }
        public bool Parsed { get; set; }
        public IParser Parser { get; private set; }
        public string Command { get; set; }
        public string CommandAfterCalculatedChild { get; set; }
        public string Result { get; protected set; }
        public string Name { get; protected set; }

        public Statement(int tokenIndex, IParser parser)
        {
            StartToken = tokenIndex;
            EndToken = tokenIndex;
            Parsed = false;
            Parser = parser;
            Name = GetType().Name;
        }

        public abstract string Calc(); // TODO: Add Method for CalcDouble and IsResultDouble

        public int CompareTo(Statement other)
        {
            var a = GetSortCode();
            var b = other.GetSortCode();

            if (a > b)
            {
                return 1;
            }
            else if (a < b)
            {
                return -1;
            }
            else
            {
                return 0;
            }
        }

        protected virtual int GetSortCode()
        {
            return StartToken;
        }
    }

    #endregion

    #region Parentheses

    public class Parentheses : Statement
    {
        public Statement Content { get; set; }
        public bool Included { get; set; }

        public Parentheses(int tokenIndex, IParser parser)
            : base(tokenIndex, parser)
        {
            Included = false;
        }

        public override string Calc()
        {
            Result = Content.Calc();
            return Result;
        }

        public override string ToString()
        {
            return String.Format("Parentheses({0}-{1}) Content: {2}", StartToken, EndToken, Content);
        }

        protected override int GetSortCode()
        {
            return base.GetSortCode() + 500;
        }
    }

    #endregion

    #region Operation

    public class Operation : Statement
    {
        public Statement Left { get; set; }
        public Statement Right { get; set; }
        public OperationType Type { get; set; }

        public Operation(int tokenIndex, OperationType type, IParser parser)
            : base(tokenIndex, parser)
        {
            Type = type;
            Name = String.Format("Operation ({0})", Type);
        }

        public override string Calc()
        {
            Parser.OnCalculating(new ParseEventArgs(Parser, this));
            double left = Convert.ToDouble(Left.Calc(), CultureInfo.InvariantCulture);
            double right = Convert.ToDouble(Right.Calc(), CultureInfo.InvariantCulture);
            double result = 0.0;

            CommandAfterCalculatedChild = Command;
            CommandAfterCalculatedChild = CommandAfterCalculatedChild.Replace(Left.Command, left.ToString(CultureInfo.InvariantCulture));
            CommandAfterCalculatedChild = CommandAfterCalculatedChild.Replace(Right.Command, right.ToString(CultureInfo.InvariantCulture));

            #region switch Type

            switch (Type)
            {
                case OperationType.Addition:
                    result = left + right;
                    break;
                case OperationType.Subtraction:
                    result = left - right;
                    break;
                case OperationType.Multiplication:
                    result = left * right;
                    break;
                case OperationType.Division:
                    result = left / right;
                    break;
                case OperationType.Remainder:
                    result = left % right;
                    break;
                case OperationType.LessThan:
                    result = (left < right ? 1 : 0);
                    break;
                case OperationType.GreaterThan:
                    result = (left > right ? 1 : 0);
                    break;
                case OperationType.LessThanOrEqual:
                    result = (left <= right ? 1 : 0);
                    break;
                case OperationType.GreaterThanOrEqual:
                    result = (left >= right ? 1 : 0);
                    break;
                case OperationType.Equal:
                    result = (left == right ? 1 : 0);
                    break;
                case OperationType.NotEqual:
                    result = (left != right ? 1 : 0);
                    break;
                case OperationType.Assignment:
                    throw new NotImplementedException();
                case OperationType.LogicalAnd:
                    throw new NotImplementedException();
                case OperationType.ConditionalAnd:
                    result = (left == 1.0 && right == 1.0 ? 1 : 0);
                    break;
                case OperationType.ConditionalOr:
                    result = (left == 1.0 || right == 1.0 ? 1 : 0);
                    break;
                case OperationType.Comma:
                    return String.Format("{0},{1}", left.ToString(CultureInfo.InvariantCulture), right.ToString(CultureInfo.InvariantCulture));
                default:
                    throw new NotImplementedException();
            }

            #endregion

            Result = result.ToString(CultureInfo.InvariantCulture);
            Parser.OnCalculated(new ParseEventArgs(Parser, this));
            return Result;
        }

        public override string ToString()
        {
            return String.Format("Operation Type: {0}, Left: {1}, Right: {2}", Type, Left, Right);
        }

        protected override int GetSortCode()
        {
            if (Type == OperationType.Multiplication || Type == OperationType.Division)
                return StartToken;
            else
                return base.GetSortCode() + 100;
        }
    }

    #endregion

    #region Condition

    public class Condition : Statement
    {
        public Statement Content { get; set; }
        public Statement FirstTrue { get; set; }
        public Statement SecondFalse { get; set; }

        public Condition(int tokenIndex, IParser parser)
            : base(tokenIndex, parser)
        { }

        public override string Calc()
        {
            Parser.OnCalculating(new ParseEventArgs(Parser, this));
            double content = Convert.ToDouble(Content.Calc(), CultureInfo.InvariantCulture);

            if (content == 1.0)
                Result = FirstTrue.Calc();
            else
                Result = SecondFalse.Calc();

            Parser.OnCalculated(new ParseEventArgs(Parser, this));
            return Result;
        }

        protected override int GetSortCode()
        {
            return base.GetSortCode() + 400;
        }
    }

    #endregion

    #region OperationType

    public enum OperationType
    {
        Addition = 10,
        Subtraction = 11,
        Multiplication = 2,
        Division = 1,
        Remainder = 20, // %
        LessThan,
        GreaterThan,
        LessThanOrEqual,
        GreaterThanOrEqual,
        Equal,
        NotEqual,
        Assignment,
        LogicalAnd, // &
        ConditionalAnd, // &&
        ConditionalOr, // ||
        Comma
    }

    #endregion

    #region Literal

    public class Literal : Statement
    {
        public string Word { get; set; }

        public Literal(int tokenIndex, string word, IParser parser)
            : base(tokenIndex, parser)
        {
            StartToken = tokenIndex;
            Word = word;
            Command = word;
            Result = word;
        }

        public override string Calc()
        {
            return Word;
        }

        public override string ToString()
        {
            return String.Format("Literal: '{0}'", Word);
        }
    }

    #endregion

    #region Quote

    public class Quote : Literal
    {
        public Quote(int tokenIndex, IParser parser)
            : base(tokenIndex, "\"", parser)
        { }

        public override string ToString()
        {
            return "Quote (" + StartToken + ")";
        }
    }

    #endregion

    #region Function

    public class Function : Statement
    {
        public Parentheses Content { get; set; }
        public FunctionType Type { get; set; }

        public Function(int tokenIndex, FunctionType type, IParser parser)
            : base(tokenIndex, parser)
        {
            Type = type;
            Name = String.Format("Function ({0})", Type);
        }

        public override string Calc()
        {
            Parser.OnCalculating(new ParseEventArgs(Parser, this));
            double result = 0.0;
            var content = Content.Content.Calc();

            switch (Type)
            {
                case FunctionType.Round:
                    var pos = content.IndexOf(',');
                    var input = Convert.ToDouble(content.Substring(0, pos), CultureInfo.InvariantCulture);
                    var decimals = Convert.ToInt32(content.Substring(pos + 1));
                    result = Math.Round(input, decimals);
                    break;
            }

            Result = result.ToString(CultureInfo.InvariantCulture);
            Parser.OnCalculated(new ParseEventArgs(Parser, this));
            return Result;
        }

        protected override int GetSortCode()
        {
            return base.GetSortCode() + 600;
        }

        public override string ToString()
        {
            return String.Format("Function Type: {0}, Content: {1}", Type, Content);
        }
    }

    #endregion

    #region FunctionType

    public enum FunctionType
    {
        Round,
        Cos,
        Sin,
        Tan
    }

    #endregion

    #region NegativNumber

    public class NegativNumber : Statement
    {
        public Statement Content { get; set; }

        public NegativNumber(int tokenIndex, IParser parser)
            : base(tokenIndex, parser)
        { }

        public override string Calc()
        {
            Parser.OnCalculating(new ParseEventArgs(Parser, this));

            double content = Convert.ToDouble(Content.Calc(), CultureInfo.InvariantCulture);
            content = content * -1;
            Result = content.ToString(CultureInfo.InvariantCulture);

            Parser.OnCalculated(new ParseEventArgs(Parser, this));
            return Result;
        }
    }

    #endregion

    #region ParameterReference

    public class ParameterReference : Statement
    {
        public IParameter ReferencedParameter { get; private set; }

        public ParameterReference(int tokenIndex, IParameter refParameter, IParser parser)
            : base(tokenIndex, parser)
        {
            ReferencedParameter = refParameter;
            CommandAfterCalculatedChild = refParameter.Value;
        }

        public override string Calc()
        {
            Parser.OnCalculating(new ParseEventArgs(Parser, this));
            var p = new Parser();
            p.Parameter = Parser.Parameter;
            Result = p.Calc(ReferencedParameter.Value);
            Parser.OnCalculated(new ParseEventArgs(Parser, this));
            return Result;
        }
    }

    #endregion
}
